package com.bestvike.stone.spring.aop;

import com.bestvike.linq.Linq;
import com.bestvike.stone.core.lang.Convert;
import com.bestvike.stone.core.lang.StringUtils;
import com.bestvike.stone.spring.context.ThreadContext;
import com.bestvike.stone.spring.controller.BaseController;
import com.bestvike.stone.spring.rest.IRequest;
import lombok.extern.apachecommons.CommonsLog;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;

/**
 * 控制器拦截器
 * Created by 许崇雷 on 2017-10-19.
 */
@CommonsLog
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public final class ControllerInterceptor {
    private static final String NAME_TOKEN = "token";
    private static final String GET_TOKEN = "getToken";

    private static Object invokeGetMethod(Object target, String methodName) {
        try {
            Method method = Linq.asEnumerable(target.getClass().getMethods())
                    .firstOrDefault(m -> StringUtils.equals(m.getName(), methodName)
                            && m.getParameterCount() == 0
                            && m.getReturnType() != Void.TYPE);
            if (method == null)
                return null;
            return method.invoke(target);
        } catch (Exception e) {
            return null;
        }
    }

    private void updateArgs(Parameter[] params, Object[] args) {
        if (!ThreadContext.exists())
            throw new RuntimeException("must init thread context first");
        //如果 token 为空,尝试从 @RequestBody 读取
        String token = ThreadContext.getToken();
        if (StringUtils.isEmpty(token)) {
            for (int i = 0; i < params.length; i++) {
                if (StringUtils.isNotEmpty(token))
                    break;
                Object arg = args[i];
                if (arg == null)
                    continue;
                Parameter param = params[i];
                if (param.getAnnotation(RequestBody.class) == null)
                    continue;
                Class<?> paramType = param.getType();
                if (Map.class.isAssignableFrom(paramType)) {
                    token = Convert.toString(((Map) args[i]).get(NAME_TOKEN));
                    continue;
                }
                token = Convert.toString(invokeGetMethod(arg, GET_TOKEN));
            }
        }
        //更新上下文中的 token
        ThreadContext.setToken(token);
        log.info(String.format("==>Common args token: %s", token));
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.GetMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.DeleteMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.PatchMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.PostMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.PutMapping)")
    private void requestMappingPointcut() {
    }

    @Around(value = "requestMappingPointcut()")
    public Object proceedRequestMappingPointcut(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取变量
        BaseController controller = Convert.asType(joinPoint.getTarget(), BaseController.class);
        MethodSignature signature = Convert.asType(joinPoint.getSignature(), MethodSignature.class);
        if (controller == null || signature == null)
            return joinPoint.proceed();
        //更新参数
        Parameter[] params = signature.getMethod().getParameters();
        Object[] args = joinPoint.getArgs();
        this.updateArgs(params, args);
        //执行
        ThreadContext.enterController(controller);//进入
        try {
            for (Object arg : args) {
                if (arg instanceof IRequest)
                    ((IRequest) arg).assertValid();
            }
            return joinPoint.proceed(args);
        } finally {
            ThreadContext.exitController();//退出
        }
    }
}
